/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

var zrUtil = require("static/plugins/js/zrender/lib/core/util");

var numberUtil = require("../util/number");

var formatUtil = require("../util/format");

var scaleHelper = require("./helper");

var IntervalScale = require("./Interval");

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance
 * with the License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing,
 * software distributed under the License is distributed on an
 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 * KIND, either express or implied.  See the License for the
 * specific language governing permissions and limitations
 * under the License.
 */

/*
 * A third-party license is embeded for some of the code in this file:
 * The "scaleLevels" was originally copied from "d3.js" with some
 * modifications made for this project.
 * (See more details in the comment on the definition of "scaleLevels" below.)
 * The use of the source code of this file is also subject to the terms
 * and consitions of the license of "d3.js" (BSD-3Clause, see
 * </licenses/LICENSE-d3>).
 */
// [About UTC and local time zone]:
// In most cases, `number.parseDate` will treat input data string as local time
// (except time zone is specified in time string). And `format.formateTime` returns
// local time by default. option.useUTC is false by default. This design have
// concidered these common case:
// (1) Time that is persistent in server is in UTC, but it is needed to be diplayed
// in local time by default.
// (2) By default, the input data string (e.g., '2011-01-02') should be displayed
// as its original time, without any time difference.
var intervalScaleProto = IntervalScale.prototype;
var mathCeil = Math.ceil;
var mathFloor = Math.floor;
var ONE_SECOND = 1000;
var ONE_MINUTE = ONE_SECOND * 60;
var ONE_HOUR = ONE_MINUTE * 60;
var ONE_DAY = ONE_HOUR * 24; // FIXME 公用？

var bisect = function (a, x, lo, hi) {
    while (lo < hi) {
        var mid = (lo + hi) >>> 1;

        if (a[mid][1] < x) {
            lo = mid + 1;
        } else {
            hi = mid;
        }
    }

    return lo;
};
/**
 * @alias module:echarts/coord/scale/Time
 * @constructor
 */

var TimeScale = IntervalScale.extend({
    type: "time",

    /**
     * @override
     */
    getLabel: function (val) {
        var stepLvl = this._stepLvl;
        var date = new Date(val);
        return formatUtil.formatTime(
            stepLvl[0],
            date,
            this.getSetting("useUTC")
        );
    },

    /**
     * @override
     */
    niceExtent: function (opt) {
        var extent = this._extent; // If extent start and end are same, expand them

        if (extent[0] === extent[1]) {
            // Expand extent
            extent[0] -= ONE_DAY;
            extent[1] += ONE_DAY;
        } // If there are no data and extent are [Infinity, -Infinity]

        if (extent[1] === -Infinity && extent[0] === Infinity) {
            var d = new Date();
            extent[1] = +new Date(d.getFullYear(), d.getMonth(), d.getDate());
            extent[0] = extent[1] - ONE_DAY;
        }

        this.niceTicks(opt.splitNumber, opt.minInterval, opt.maxInterval); // var extent = this._extent;

        var interval = this._interval;

        if (!opt.fixMin) {
            extent[0] = numberUtil.round(
                mathFloor(extent[0] / interval) * interval
            );
        }

        if (!opt.fixMax) {
            extent[1] = numberUtil.round(
                mathCeil(extent[1] / interval) * interval
            );
        }
    },

    /**
     * @override
     */
    niceTicks: function (approxTickNum, minInterval, maxInterval) {
        approxTickNum = approxTickNum || 10;
        var extent = this._extent;
        var span = extent[1] - extent[0];
        var approxInterval = span / approxTickNum;

        if (minInterval != null && approxInterval < minInterval) {
            approxInterval = minInterval;
        }

        if (maxInterval != null && approxInterval > maxInterval) {
            approxInterval = maxInterval;
        }

        var scaleLevelsLen = scaleLevels.length;
        var idx = bisect(scaleLevels, approxInterval, 0, scaleLevelsLen);
        var level = scaleLevels[Math.min(idx, scaleLevelsLen - 1)];
        var interval = level[1]; // Same with interval scale if span is much larger than 1 year

        if (level[0] === "year") {
            var yearSpan = span / interval; // From "Nice Numbers for Graph Labels" of Graphic Gems
            // var niceYearSpan = numberUtil.nice(yearSpan, false);

            var yearStep = numberUtil.nice(yearSpan / approxTickNum, true);
            interval *= yearStep;
        }

        var timezoneOffset = this.getSetting("useUTC")
            ? 0
            : new Date(+extent[0] || +extent[1]).getTimezoneOffset() *
              60 *
              1000;
        var niceExtent = [
            Math.round(
                mathCeil((extent[0] - timezoneOffset) / interval) * interval +
                    timezoneOffset
            ),
            Math.round(
                mathFloor((extent[1] - timezoneOffset) / interval) * interval +
                    timezoneOffset
            ),
        ];
        scaleHelper.fixExtent(niceExtent, extent);
        this._stepLvl = level; // Interval will be used in getTicks

        this._interval = interval;
        this._niceExtent = niceExtent;
    },
    parse: function (val) {
        // val might be float.
        return +numberUtil.parseDate(val);
    },
});
zrUtil.each(["contain", "normalize"], function (methodName) {
    TimeScale.prototype[methodName] = function (val) {
        return intervalScaleProto[methodName].call(this, this.parse(val));
    };
});
/**
 * This implementation was originally copied from "d3.js"
 * <https://github.com/d3/d3/blob/b516d77fb8566b576088e73410437494717ada26/src/time/scale.js>
 * with some modifications made for this program.
 * See the license statement at the head of this file.
 */

var scaleLevels = [
    // Format              interval
    ["hh:mm:ss", ONE_SECOND], // 1s
    ["hh:mm:ss", ONE_SECOND * 5], // 5s
    ["hh:mm:ss", ONE_SECOND * 10], // 10s
    ["hh:mm:ss", ONE_SECOND * 15], // 15s
    ["hh:mm:ss", ONE_SECOND * 30], // 30s
    ["hh:mm\nMM-dd", ONE_MINUTE], // 1m
    ["hh:mm\nMM-dd", ONE_MINUTE * 5], // 5m
    ["hh:mm\nMM-dd", ONE_MINUTE * 10], // 10m
    ["hh:mm\nMM-dd", ONE_MINUTE * 15], // 15m
    ["hh:mm\nMM-dd", ONE_MINUTE * 30], // 30m
    ["hh:mm\nMM-dd", ONE_HOUR], // 1h
    ["hh:mm\nMM-dd", ONE_HOUR * 2], // 2h
    ["hh:mm\nMM-dd", ONE_HOUR * 6], // 6h
    ["hh:mm\nMM-dd", ONE_HOUR * 12], // 12h
    ["MM-dd\nyyyy", ONE_DAY], // 1d
    ["MM-dd\nyyyy", ONE_DAY * 2], // 2d
    ["MM-dd\nyyyy", ONE_DAY * 3], // 3d
    ["MM-dd\nyyyy", ONE_DAY * 4], // 4d
    ["MM-dd\nyyyy", ONE_DAY * 5], // 5d
    ["MM-dd\nyyyy", ONE_DAY * 6], // 6d
    ["week", ONE_DAY * 7], // 7d
    ["MM-dd\nyyyy", ONE_DAY * 10], // 10d
    ["week", ONE_DAY * 14], // 2w
    ["week", ONE_DAY * 21], // 3w
    ["month", ONE_DAY * 31], // 1M
    ["week", ONE_DAY * 42], // 6w
    ["month", ONE_DAY * 62], // 2M
    ["week", ONE_DAY * 70], // 10w
    ["quarter", ONE_DAY * 95], // 3M
    ["month", ONE_DAY * 31 * 4], // 4M
    ["month", ONE_DAY * 31 * 5], // 5M
    ["half-year", (ONE_DAY * 380) / 2], // 6M
    ["month", ONE_DAY * 31 * 8], // 8M
    ["month", ONE_DAY * 31 * 10], // 10M
    ["year", ONE_DAY * 380], // 1Y
];
/**
 * @param {module:echarts/model/Model}
 * @return {module:echarts/scale/Time}
 */

TimeScale.create = function (model) {
    return new TimeScale({
        useUTC: model.ecModel.get("useUTC"),
    });
};

var _default = TimeScale;
module.exports = _default;
